#!/usr/bin/env python

# Copyright (c) 2018 Intel Labs.
# authors: German Ros (german.ros@intel.com)
#
# This work is licensed under the terms of the MIT license.
# For a copy, see <https://opensource.org/licenses/MIT>.

"""Example of automatic vehicle control from client side, with a python defined agent"""
#########################################################
#####在extract2.py的基础上修改完善 增加其余列（2是45列）
import argparse
import collections
import datetime
import logging
import math
import os
import numpy.random as random
import re
import sys
import weakref
import glob
import math
import torch
from model.model import Transfollower
try:
    import pygame
    from pygame.locals import KMOD_CTRL
    from pygame.locals import K_ESCAPE
    from pygame.locals import K_q
except ImportError:
    raise RuntimeError('cannot import pygame, make sure pygame package is installed')

try:
    import numpy as np
except ImportError:
    raise RuntimeError(
        'cannot import numpy, make sure numpy package is installed')
try:
    sys.path.append(glob.glob('../carla/dist/carla-*%d.%d-%s.egg' % (
        sys.version_info.major,
        sys.version_info.minor,
        'win-amd64' if os.name == 'nt' else 'linux-x86_64'))[0])
except IndexError:
    pass
# ==============================================================================
# -- Add PythonAPI for release mode --------------------------------------------
# ==============================================================================
try:
    sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + '/carla')
except IndexError:
    pass
import sys
sys.path.append("D:/CARLA_0.9.15/WindowsNoEditor/PythonAPI")
sys.path.append("D:/CARLA_0.9.15/WindowsNoEditor/PythonAPI/carla")
import carla
from carla import ColorConverter as cc
from config import Settings, HighDSettings

from agents.navigation.behavior_agent import BehaviorAgent  # ✅ 正确
from agents.navigation.basic_agent import BasicAgent

import csv
model_path_1 = "D:\CARLA_0.9.15\WindowsNoEditor\PythonAPI\examples\大电脑模型存放\highD_transfollower_model.pt"  # 你的模型1路径
model_path_2 = "D:\CARLA_0.9.15\WindowsNoEditor\PythonAPI\examples\大电脑模型存放\highD_transfollower_model_x.pt"  # 你的模型2路径

def get_distance(location1, location2):
    dx = location1.x - location2.x
    dy = location1.y - location2.y
    return math.sqrt(dx ** 2 + dy ** 2)


# ==============================================================================
# -- Global functions ----------------------------------------------------------
# ==============================================================================
# 初始化 CSV 文件
csv_file = "carla_log.csv"
with open(csv_file, mode='a', newline='') as file:
    writer = csv.writer(file)
    # writer.writerow(["Frame", "Weather", "Vehicle Ahead", "Pedestrians Ahead", "Static Obstacles (within 100m)", "Traffic Lights Ahead", "Traffic Signs Ahead","Control Info"])

#判断是否是交叉口
config = HighDSettings
LANE_TYPE_MAP = {
    getattr(carla.LaneType, "None", None): 0,
    carla.LaneType.Driving: 1,        # 机动车道
    carla.LaneType.Shoulder: 2,       # 应急车道
    carla.LaneType.Sidewalk: 3,       # 人行道
    carla.LaneType.Biking: 4,           # 自行车道
    carla.LaneType.Parking: 5,        # 停车道
              # 无车道
}



def world_to_vehicle(vehicle, point):
    """
    将世界坐标系中的点转换到自车坐标系
    """
    vehicle_transform = vehicle.get_transform()
    vehicle_location = vehicle_transform.location
    vehicle_yaw = math.radians(vehicle_transform.rotation.yaw)

    # 计算相对位置
    dx = point.x - vehicle_location.x
    dy = point.y - vehicle_location.y

    # 旋转变换 (让车辆前方为 y 轴)
    x_vehicle = dx * math.cos(-vehicle_yaw) - dy * math.sin(-vehicle_yaw)
    y_vehicle = dx * math.sin(-vehicle_yaw) + dy * math.cos(-vehicle_yaw)


    return np.array([x_vehicle, y_vehicle])


def get_traffic_light_state(state):
    """将 `TrafficLightState` 转换为数字"""
    if state == carla.TrafficLightState.Red:
        return 1
    elif state == carla.TrafficLightState.Green:
        return 2
    elif state == carla.TrafficLightState.Yellow:
        return 3
    else:
        return 0  # 无红绿灯或未知状态


def get_relevant_traffic_lights(vehicle):
    """
    🚦 通过 `trigger_volume` 识别当前控制车辆的红绿灯
    只返回影响当前车道的红绿灯，避免误选其他方向的灯
    """
    world = vehicle.get_world()
    traffic_lights = world.get_actors().filter("traffic.traffic_light")
    relevant_lights = []

    for light in traffic_lights:
        # 获取红绿灯的触发范围
        trigger_volume = light.trigger_volume
        light_transform = light.get_transform()
        trigger_location = light_transform.transform(trigger_volume.location)  # 计算触发区域的世界坐标

        # 计算与车辆的距离
        vehicle_location = vehicle.get_location()
        distance = vehicle_location.distance(trigger_location)

        # 如果车辆在触发区域范围内，则该红绿灯有效
        if distance < 40:  # 触发范围可以调整
            relevant_lights.append(light)

    return relevant_lights


def get_lane_info(vehicle):
    """
    获取车辆所在的车道信息，并检测：
    - 车道 ID
    - 车道类型
    - 是否处于交叉口
    - 是否有 STOP 标志
    - 左转、直行、右转红绿灯状态
    """
    world = vehicle.get_world()
    carla_map = world.get_map()
    waypoint = carla_map.get_waypoint(vehicle.get_location())

    lane_id = waypoint.lane_id  # 车道 ID
    lane_type = LANE_TYPE_MAP.get(waypoint.lane_type, 0)
    is_intersection = 1 if waypoint.is_intersection else 0  # 是否在交叉口

    # 🚦 检测 STOP 标志
    has_stop_sign = 0
    stop_signs = world.get_actors().filter("traffic.stop")
    for stop in stop_signs:
        if vehicle.get_location().distance(stop.get_location()) < 40.0:
            has_stop_sign = 1
            break

    # 🚦 只获取当前车道的红绿灯
    traffic_lights = get_relevant_traffic_lights(vehicle)

    traffic_light_positions = []
    for light in traffic_lights:
        state = light.get_state()
        local_pos = world_to_vehicle(vehicle, light.get_transform().location)
        traffic_light_positions.append((local_pos, state))

    # 按 y 坐标排序（确保越前方的灯优先）
    traffic_light_positions.sort(key=lambda t: t[0][1], reverse=True)

    # 初始化默认值
    left_traffic_light = 0
    straight_traffic_light = 0
    right_traffic_light = 0

    if len(traffic_light_positions) == 3:
        left_traffic_light = get_traffic_light_state(traffic_light_positions[0][1])
        straight_traffic_light = get_traffic_light_state(traffic_light_positions[1][1])
        right_traffic_light = get_traffic_light_state(traffic_light_positions[2][1])

    elif len(traffic_light_positions) == 2:
        positions = sorted(traffic_light_positions, key=lambda t: t[0][0])
        left_traffic_light = get_traffic_light_state(positions[0][1])
        straight_traffic_light = get_traffic_light_state(positions[1][1])
        right_traffic_light = 0

    return lane_id, lane_type, is_intersection, has_stop_sign, left_traffic_light, straight_traffic_light, right_traffic_light


def get_vehicle_state(vehicle, ego):
    """获取车辆在自车坐标系下的横向速度、纵向速度，以及与ego车的距离。"""
    # 获取全局速度
    velocity = vehicle.get_velocity()

    # 获取车辆的航向角（世界坐标系下的朝向）
    vehicle_transform = vehicle.get_transform()
    yaw = math.radians(vehicle_transform.rotation.yaw)  # 转换为弧度

    # 旋转变换（世界坐标系 → 自车坐标系）
    v_x_local = velocity.x * math.cos(-yaw) - velocity.y * math.sin(-yaw)  # 纵向速度
    v_y_local = velocity.x * math.sin(-yaw) + velocity.y * math.cos(-yaw)  # 横向速度

    # 计算与ego车的距离
    distance = vehicle.get_location().distance(ego.get_location())
    # print(f"前车纵向速度: {v_x_local}, 前车横向速度: {v_y_local},前车距离: {distance}")
    return v_x_local, v_y_local, distance

##############2025.3.3修改为自车坐标系
def get_relative_position(ego_vehicle, actor):
    """计算目标对象相对于自车的局部坐标（自车为原点，前进方向为 Y 轴，左侧为 X 轴）"""

    # 获取自车和目标物体的位置
    ego_location = ego_vehicle.get_location()
    actor_location = actor.get_location()

    # 计算世界坐标系下的相对位置
    x_world = actor_location.x - ego_location.x
    y_world = actor_location.y - ego_location.y

    # 获取自车的航向角（Yaw 角，单位是度，需要转换成弧度）
    ego_yaw = math.radians(ego_vehicle.get_transform().rotation.yaw)

    # 进行坐标变换，使其以自车为中心
    x_local = math.cos(ego_yaw) * x_world + math.sin(ego_yaw) * y_world
    y_local = -math.sin(ego_yaw) * x_world + math.cos(ego_yaw) * y_world
    # print(f"纵坐标: {x_local}, 横坐标: {y_local}")
    return x_local, y_local


def get_obj_state(obj, ego):
    bounding_box = obj.bounding_box
    length = bounding_box.extent.x * 2  # Full length (multiply by 2)
    width = bounding_box.extent.y * 2  # Full width (multiply by 2)
    height = bounding_box.extent.z * 2  # Full height (multiply by 2)

    length = length if math.isfinite(length) else -1
    width = width if math.isfinite(width) else -1
    height = height if math.isfinite(height) else -1

    x_relative, y_relative = get_relative_position(ego, obj)
    return x_relative, y_relative, length, width, height




def get_objects_in_range(ego_vehicle, world, left_side=True):
    """
    查找 40m 前后、10m 左右范围内的所有物体，并返回最近 4 个目标。
    坐标转换到以自车为中心的局部坐标系：
    - Y 轴：前进方向（正前方为正）
    - X 轴：垂直方向（左侧为负，右侧为正）
    """
    obstacles_in_range = []
    ego_transform = ego_vehicle.get_transform()
    ego_location = ego_transform.location
    ego_yaw = math.radians(ego_transform.rotation.yaw)  # 角度转弧度

    all_objects = world.get_actors()  # 获取所有物体，包括车辆、行人、路牌等

    def world_to_local(obj):
        """将 CARLA 世界坐标转换为自车局部坐标"""
        obj_location = obj.get_location()
        dx = obj_location.x - ego_location.x
        dy = obj_location.y - ego_location.y

        # 旋转变换：世界坐标 -> 局部坐标
        x_rel = math.cos(ego_yaw) * dx + math.sin(ego_yaw) * dy
        y_rel = -math.sin(ego_yaw) * dx + math.cos(ego_yaw) * dy

        return x_rel, y_rel, obj

    for obj in all_objects:
        if obj.id != ego_vehicle.id:  # 排除自车
            x_rel, y_rel, obj = world_to_local(obj)

            # 仅保留 40m 纵向范围 & 10m 横向范围内的目标
            if abs(y_rel) <= 40 and abs(x_rel) <= 10:
                if (left_side and x_rel < 0) or (not left_side and x_rel > 0):
                    obstacles_in_range.append((obj, math.sqrt(x_rel**2 + y_rel**2)))

    # 按距离排序，返回最近 4 个
    obstacles_in_range = sorted(obstacles_in_range, key=lambda obj: obj[1])[:4]

    return [obj[0] for obj in obstacles_in_range]  # 仅返回对象本身



# def get_vehicle_ahead(ego_vehicle, world):
#     """
#     获取自车前方最近的车辆，要求距离自车 120 米以内，
#     且横向距离在 1.5 米以内，同时考虑自车的朝向。
#     """
#     ego_location = ego_vehicle.get_location()  # 获取自车的位置
#     ego_transform = ego_vehicle.get_transform()  # 获取自车的变换信息（位置 + 朝向）
#     ego_yaw = math.radians(ego_transform.rotation.yaw)  # 将自车的朝向角度转为弧度
#
#     vehicles = world.get_actors().filter("vehicle.*")  # 获取世界中的所有车辆
#     closest_vehicle = None
#     closest_distance = float('inf')  # 初始化最小距离为无穷大
#
#     for vehicle in vehicles:
#         if vehicle.id == ego_vehicle.id:  # 跳过自车
#             continue
#
#         # 获取其他车辆的位置
#         actor_location = vehicle.get_location()
#         distance = get_distance(ego_location, actor_location)
#
#         # 检查车辆是否在 120 米内且横向距离在 1.5 米内
#         lateral_distance = abs(actor_location.y - ego_location.y)
#
#         if distance <= 120.0 and lateral_distance <= 1.5:
#             # 计算车辆相对于自车的位置
#             dx = actor_location.x - ego_location.x
#             dy = actor_location.y - ego_location.y
#
#             # 判断该车辆是否在自车前方（基于自车的朝向）
#             # 将相对位置投影到自车的前进方向
#             forward_x = math.cos(ego_yaw) * dx + math.sin(ego_yaw) * dy
#             if forward_x > 0:  # 如果投影值大于 0，说明车辆在自车前方
#                 if distance < closest_distance:
#                     closest_vehicle = vehicle
#                     closest_distance = distance
#
#     return closest_vehicle

############重新写前方最近车辆
def get_vehicle_ahead(ego_vehicle, world):
    """
    获取自车前方最近的车辆，要求距离自车 120 米以内，
    且横向距离在 1.5 米以内，同时考虑自车的朝向。
    """
    ego_location = ego_vehicle.get_location()  # 获取自车的位置
    ego_transform = ego_vehicle.get_transform()  # 获取自车的变换信息（位置 + 朝向）
    ego_yaw = math.radians(ego_transform.rotation.yaw)  # 将自车的朝向角度转为弧度

    vehicles = world.get_actors().filter("vehicle.*")  # 获取世界中的所有车辆
    closest_vehicle = None
    closest_distance = float('inf')  # 初始化最小距离为无穷大

    for vehicle in vehicles:
        if vehicle.id == ego_vehicle.id:  # 跳过自车
            continue

        # 获取其他车辆的位置
        actor_location = vehicle.get_location()

        # 转换车辆位置到自车坐标系
        vehicle_position_in_car_coord = world_to_vehicle(ego_vehicle, actor_location)

        # 获取自车坐标系下的前向距离（x轴方向）和横向距离（y轴方向）
        x_vehicle = vehicle_position_in_car_coord[0]
        y_vehicle = vehicle_position_in_car_coord[1]

        # 检查车辆是否在前方120米内，且横向距离在1.5米以内
        if 0 < x_vehicle <= 120.0 and abs(y_vehicle) <= 1.5:
            if x_vehicle < closest_distance:  # 如果车辆距离更近，更新最近车辆
                closest_vehicle = vehicle
                closest_distance = x_vehicle

    return closest_vehicle

def get_surrounding_objects_data(ego_vehicle, world):
    left_objects = get_objects_in_range(ego_vehicle, world, left_side=True)
    right_objects = get_objects_in_range(ego_vehicle, world, left_side=False)
    return left_objects, right_objects


# 记录每帧信息的函数
frame_data_list = []

def log_frame_data(world, ego_vehicle):
    result = []
    ego_velocity = ego_vehicle.get_velocity()
    closest_vehicle = get_vehicle_ahead(ego_vehicle, world)
    v_x, v_y, distance = 0.0, 0.0, 0.0
    if closest_vehicle is not None:
        v_x, v_y, distance = get_vehicle_state(closest_vehicle, ego_vehicle)
    left_objects = get_objects_in_range(ego_vehicle, world, left_side=True)
    right_objects = get_objects_in_range(ego_vehicle, world, left_side=False)

    # 获取自车坐标系下的速度
    vehicle_transform = ego_vehicle.get_transform()
    yaw = math.radians(vehicle_transform.rotation.yaw)  # 获取自车航向角，并转换为弧度

    # 将全局速度转换到自车坐标系
    ego_v_x_local = ego_velocity.x * math.cos(-yaw) - ego_velocity.y * math.sin(-yaw)
    ego_v_y_local = ego_velocity.x * math.sin(-yaw) + ego_velocity.y * math.cos(-yaw)

    result.append(ego_v_x_local)
    result.append(ego_v_y_local)
    result.append(v_x)
    result.append(v_y)
    result.append(distance)

    # 🚦 记录是否在交叉口
    # 🛣 记录车道信息（数值化）
    lane_id, lane_type, is_intersection, has_stop_sign, left_traffic_light, straight_traffic_light, right_traffic_light = get_lane_info(
        ego_vehicle)
    result.append(lane_id)  # 车道编号
    result.append(lane_type)  # 车道类型（数字）
    result.append(is_intersection)
    result.append(has_stop_sign)  # 是否有 STOP 标志 (0/1)
    result.append(left_traffic_light)  # 左转红绿灯 (0/1/2/3)
    result.append(straight_traffic_light)  # 直行红绿灯 (0/1/2/3)
    result.append(right_traffic_light)  # 右转红绿灯 (0/1/2/3)

    if len(left_objects) > 0:
        for i, obj in enumerate(left_objects[:4]):  # 只取前 4 个
            x_relative, y_relative, length, width, height = get_obj_state(obj, ego_vehicle)
            result.extend([x_relative, y_relative, length, width, height])

        # 若不足 4 个物体，用 0 填充
        for _ in range(4 - len(left_objects)):
            result.extend([0.0, 0.0, 0.0, 0.0, 0.0])
    else:
        # 若没有物体，直接填充 4 组 0
        for _ in range(4):
            result.extend([0.0, 0.0, 0.0, 0.0, 0.0])

    if len(right_objects) > 0:
        for i, obj in enumerate(right_objects[:4]):  # 只取前 4 个
            x_relative, y_relative, length, width, height = get_obj_state(obj, ego_vehicle)
            result.extend([x_relative, y_relative, length, width, height])

        # 若不足 4 个物体，用 0 填充
        for _ in range(4 - len(right_objects)):
            result.extend([0.0, 0.0, 0.0, 0.0, 0.0])
    else:
        # 若没有物体，直接填充 4 组 0
        for _ in range(4):
            result.extend([0.0, 0.0, 0.0, 0.0, 0.0])

    # 生成 8 个掩码列
    mask_indices = [-36, -31, -26, -21, -16, -11, -6, -1]  # 需要检查的列索引
    mask_values = [(1 if result[idx] != 0 else 0) for idx in mask_indices]  # 计算掩码值
    result.extend(mask_values)  # 添加掩码列

    # 确保数据结构一致
    expected_columns = 60  # 52 原始列 + 8 掩码列
    assert len(result) == expected_columns, f"列数错误，期望 {expected_columns}，实际 {len(result)}"

    # **存储到列表，而不是 CSV**
    frame_data_list.append(result)

    return result  # 直接返回数据


def find_weather_presets():
    """Method to find weather presets"""
    rgx = re.compile('.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)')

    def name(x): return ' '.join(m.group(0) for m in rgx.finditer(x))

    presets = [x for x in dir(carla.WeatherParameters) if re.match('[A-Z].+', x)]
    return [(getattr(carla.WeatherParameters, x), name(x)) for x in presets]


def get_actor_display_name(actor, truncate=250):
    """Method to get actor display name"""
    name = ' '.join(actor.type_id.replace('_', '.').title().split('.')[1:])
    return (name[:truncate - 1] + u'\u2026') if len(name) > truncate else name


def get_actor_blueprints(world, filter, generation):
    bps = world.get_blueprint_library().filter(filter)

    if generation.lower() == "all":
        return bps

    # If the filter returns only one bp, we assume that this one needed
    # and therefore, we ignore the generation
    if len(bps) == 1:
        return bps

    try:
        int_generation = int(generation)
        # Check if generation is in available generations
        if int_generation in [1, 2, 3]:
            bps = [x for x in bps if int(x.get_attribute('generation')) == int_generation]
            return bps
        else:
            print("   Warning! Actor Generation is not valid. No actor will be spawned.")
            return []
    except:
        print("   Warning! Actor Generation is not valid. No actor will be spawned.")
        return []


# ==============================================================================
# -- World ---------------------------------------------------------------
# ==============================================================================

class World(object):
    """ Class representing the surrounding environment """

    def __init__(self, carla_world, hud, args):
        """Constructor method"""
        self._args = args
        self.world = carla_world
        try:
            self.map = self.world.get_map()
        except RuntimeError as error:
            print('RuntimeError: {}'.format(error))
            print('  The server could not send the OpenDRIVE (.xodr) file:')
            print('  Make sure it exists, has the same name of your town, and is correct.')
            sys.exit(1)
        self.hud = hud
        self.player = None
        self.collision_sensor = None
        self.lane_invasion_sensor = None
        self.gnss_sensor = None
        self.camera_manager = None
        self._weather_presets = find_weather_presets()
        self._weather_index = 0
        self._actor_filter = args.filter
        self._actor_generation = args.generation
        self.restart(args)
        self.world.on_tick(hud.on_world_tick)
        self.recording_enabled = False
        self.recording_start = 0

    def restart(self, args):
        """Restart the world"""
        # Keep same camera config if the camera manager exists.
        cam_index = self.camera_manager.index if self.camera_manager is not None else 0
        cam_pos_id = self.camera_manager.transform_index if self.camera_manager is not None else 0

        # Get a random blueprint.
        blueprint_list = get_actor_blueprints(self.world, self._actor_filter, self._actor_generation)
        if not blueprint_list:
            raise ValueError("Couldn't find any blueprints with the specified filters")
        blueprint = random.choice(blueprint_list)
        blueprint.set_attribute('role_name', 'hero')
        if blueprint.has_attribute('color'):
            color = random.choice(blueprint.get_attribute('color').recommended_values)
            blueprint.set_attribute('color', color)

        # Spawn the player.
        if self.player is not None:
            spawn_point = self.player.get_transform()
            spawn_point.location.z += 2.0
            spawn_point.rotation.roll = 0.0
            spawn_point.rotation.pitch = 0.0
            self.destroy()
            self.player = self.world.try_spawn_actor(blueprint, spawn_point)
            self.modify_vehicle_physics(self.player)
        while self.player is None:
            if not self.map.get_spawn_points():
                print('There are no spawn points available in your map/town.')
                print('Please add some Vehicle Spawn Point to your UE4 scene.')
                sys.exit(1)
            spawn_points = self.map.get_spawn_points()
            spawn_point = random.choice(spawn_points) if spawn_points else carla.Transform()
            self.player = self.world.try_spawn_actor(blueprint, spawn_point)
            self.modify_vehicle_physics(self.player)

        if self._args.sync:
            self.world.tick()
        else:
            self.world.wait_for_tick()

        # Set up the sensors.
        self.collision_sensor = CollisionSensor(self.player, self.hud)
        self.lane_invasion_sensor = LaneInvasionSensor(self.player, self.hud)
        self.gnss_sensor = GnssSensor(self.player)
        self.camera_manager = CameraManager(self.player, self.hud)
        self.camera_manager.transform_index = cam_pos_id
        self.camera_manager.set_sensor(cam_index, notify=False)
        actor_type = get_actor_display_name(self.player)
        self.hud.notification(actor_type)

    def next_weather(self, reverse=False):
        """Get next weather setting"""
        self._weather_index += -1 if reverse else 1
        self._weather_index %= len(self._weather_presets)
        preset = self._weather_presets[self._weather_index]
        self.hud.notification('Weather: %s' % preset[1])
        self.player.get_world().set_weather(preset[0])

    def modify_vehicle_physics(self, actor):
        # If actor is not a vehicle, we cannot use the physics control
        try:
            physics_control = actor.get_physics_control()
            physics_control.use_sweep_wheel_collision = True
            actor.apply_physics_control(physics_control)
        except Exception:
            pass

    def tick(self, clock):
        """Method for every tick"""
        self.hud.tick(self, clock)

    def render(self, display):
        """Render world"""
        self.camera_manager.render(display)
        self.hud.render(display)

    def destroy_sensors(self):
        """Destroy sensors"""
        self.camera_manager.sensor.destroy()
        self.camera_manager.sensor = None
        self.camera_manager.index = None

    def destroy(self):
        """Destroys all actors"""
        actors = [
            self.camera_manager.sensor,
            self.collision_sensor.sensor,
            self.lane_invasion_sensor.sensor,
            self.gnss_sensor.sensor,
            self.player]
        for actor in actors:
            if actor is not None:
                actor.destroy()


# ==============================================================================
# -- KeyboardControl -----------------------------------------------------------
# ==============================================================================


class KeyboardControl(object):
    def __init__(self, world):
        world.hud.notification("Press 'H' or '?' for help.", seconds=4.0)

    def parse_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return True
            if event.type == pygame.KEYUP:
                if self._is_quit_shortcut(event.key):
                    return True

    @staticmethod
    def _is_quit_shortcut(key):
        """Shortcut for quitting"""
        return (key == K_ESCAPE) or (key == K_q and pygame.key.get_mods() & KMOD_CTRL)


# ==============================================================================
# -- HUD -----------------------------------------------------------------------
# ==============================================================================


class HUD(object):
    """Class for HUD text"""

    def __init__(self, width, height):
        """Constructor method"""
        self.dim = (width, height)
        font = pygame.font.Font(pygame.font.get_default_font(), 20)
        font_name = 'courier' if os.name == 'nt' else 'mono'
        fonts = [x for x in pygame.font.get_fonts() if font_name in x]
        default_font = 'ubuntumono'
        mono = default_font if default_font in fonts else fonts[0]
        mono = pygame.font.match_font(mono)
        self._font_mono = pygame.font.Font(mono, 12 if os.name == 'nt' else 14)
        self._notifications = FadingText(font, (width, 40), (0, height - 40))
        self.help = HelpText(pygame.font.Font(mono, 24), width, height)
        self.server_fps = 0
        self.frame = 0
        self.simulation_time = 0
        self._show_info = True
        self._info_text = []
        self._server_clock = pygame.time.Clock()

    def on_world_tick(self, timestamp):
        """Gets informations from the world at every tick"""
        self._server_clock.tick()
        self.server_fps = self._server_clock.get_fps()
        self.frame = timestamp.frame_count
        self.simulation_time = timestamp.elapsed_seconds

    def tick(self, world, clock):
        """HUD method for every tick"""
        self._notifications.tick(world, clock)
        if not self._show_info:
            return
        transform = world.player.get_transform()
        vel = world.player.get_velocity()
        control = world.player.get_control()
        heading = 'N' if abs(transform.rotation.yaw) < 89.5 else ''
        heading += 'S' if abs(transform.rotation.yaw) > 90.5 else ''
        heading += 'E' if 179.5 > transform.rotation.yaw > 0.5 else ''
        heading += 'W' if -0.5 > transform.rotation.yaw > -179.5 else ''
        colhist = world.collision_sensor.get_collision_history()
        collision = [colhist[x + self.frame - 200] for x in range(0, 200)]
        max_col = max(1.0, max(collision))
        collision = [x / max_col for x in collision]
        vehicles = world.world.get_actors().filter('vehicle.*')

        self._info_text = [
            'Server:  % 16.0f FPS' % self.server_fps,
            'Client:  % 16.0f FPS' % clock.get_fps(),
            '',
            'Vehicle: % 20s' % get_actor_display_name(world.player, truncate=20),
            'Map:     % 20s' % world.map.name.split('/')[-1],
            'Simulation time: % 12s' % datetime.timedelta(seconds=int(self.simulation_time)),
            '',
            'Speed:   % 15.0f km/h' % (3.6 * math.sqrt(vel.x ** 2 + vel.y ** 2 + vel.z ** 2)),
            u'Heading:% 16.0f\N{DEGREE SIGN} % 2s' % (transform.rotation.yaw, heading),
            'Location:% 20s' % ('(% 5.1f, % 5.1f)' % (transform.location.x, transform.location.y)),
            'GNSS:% 24s' % ('(% 2.6f, % 3.6f)' % (world.gnss_sensor.lat, world.gnss_sensor.lon)),
            'Height:  % 18.0f m' % transform.location.z,
            '']
        if isinstance(control, carla.VehicleControl):
            self._info_text += [
                ('Throttle:', control.throttle, 0.0, 1.0),
                ('Steer:', control.steer, -1.0, 1.0),
                ('Brake:', control.brake, 0.0, 1.0),
                ('Reverse:', control.reverse),
                ('Hand brake:', control.hand_brake),
                ('Manual:', control.manual_gear_shift),
                'Gear:        %s' % {-1: 'R', 0: 'N'}.get(control.gear, control.gear)]
        elif isinstance(control, carla.WalkerControl):
            self._info_text += [
                ('Speed:', control.speed, 0.0, 5.556),
                ('Jump:', control.jump)]
        self._info_text += [
            '',
            'Collision:',
            collision,
            '',
            'Number of vehicles: % 8d' % len(vehicles)]

        if len(vehicles) > 1:
            self._info_text += ['Nearby vehicles:']

        def dist(l):
            return math.sqrt((l.x - transform.location.x) ** 2 + (l.y - transform.location.y)
                             ** 2 + (l.z - transform.location.z) ** 2)

        vehicles = [(dist(x.get_location()), x) for x in vehicles if x.id != world.player.id]

        for dist, vehicle in sorted(vehicles):
            if dist > 200.0:
                break
            vehicle_type = get_actor_display_name(vehicle, truncate=22)
            self._info_text.append('% 4dm %s' % (dist, vehicle_type))

    def toggle_info(self):
        """Toggle info on or off"""
        self._show_info = not self._show_info

    def notification(self, text, seconds=2.0):
        """Notification text"""
        self._notifications.set_text(text, seconds=seconds)

    def error(self, text):
        """Error text"""
        self._notifications.set_text('Error: %s' % text, (255, 0, 0))

    def render(self, display):
        """Render for HUD class"""
        if self._show_info:
            info_surface = pygame.Surface((220, self.dim[1]))
            info_surface.set_alpha(100)
            display.blit(info_surface, (0, 0))
            v_offset = 4
            bar_h_offset = 100
            bar_width = 106
            for item in self._info_text:
                if v_offset + 18 > self.dim[1]:
                    break
                if isinstance(item, list):
                    if len(item) > 1:
                        points = [(x + 8, v_offset + 8 + (1 - y) * 30) for x, y in enumerate(item)]
                        pygame.draw.lines(display, (255, 136, 0), False, points, 2)
                    item = None
                    v_offset += 18
                elif isinstance(item, tuple):
                    if isinstance(item[1], bool):
                        rect = pygame.Rect((bar_h_offset, v_offset + 8), (6, 6))
                        pygame.draw.rect(display, (255, 255, 255), rect, 0 if item[1] else 1)
                    else:
                        rect_border = pygame.Rect((bar_h_offset, v_offset + 8), (bar_width, 6))
                        pygame.draw.rect(display, (255, 255, 255), rect_border, 1)
                        fig = (item[1] - item[2]) / (item[3] - item[2])
                        if item[2] < 0.0:
                            rect = pygame.Rect(
                                (bar_h_offset + fig * (bar_width - 6), v_offset + 8), (6, 6))
                        else:
                            rect = pygame.Rect((bar_h_offset, v_offset + 8), (fig * bar_width, 6))
                        pygame.draw.rect(display, (255, 255, 255), rect)
                    item = item[0]
                if item:  # At this point has to be a str.
                    surface = self._font_mono.render(item, True, (255, 255, 255))
                    display.blit(surface, (8, v_offset))
                v_offset += 18
        self._notifications.render(display)
        self.help.render(display)


# ==============================================================================
# -- FadingText ----------------------------------------------------------------
# ==============================================================================


class FadingText(object):
    """ Class for fading text """

    def __init__(self, font, dim, pos):
        """Constructor method"""
        self.font = font
        self.dim = dim
        self.pos = pos
        self.seconds_left = 0
        self.surface = pygame.Surface(self.dim)

    def set_text(self, text, color=(255, 255, 255), seconds=2.0):
        """Set fading text"""
        text_texture = self.font.render(text, True, color)
        self.surface = pygame.Surface(self.dim)
        self.seconds_left = seconds
        self.surface.fill((0, 0, 0, 0))
        self.surface.blit(text_texture, (10, 11))

    def tick(self, _, clock):
        """Fading text method for every tick"""
        delta_seconds = 1e-3 * clock.get_time()
        self.seconds_left = max(0.0, self.seconds_left - delta_seconds)
        self.surface.set_alpha(500.0 * self.seconds_left)

    def render(self, display):
        """Render fading text method"""
        display.blit(self.surface, self.pos)


# ==============================================================================
# -- HelpText ------------------------------------------------------------------
# ==============================================================================


class HelpText(object):
    """ Helper class for text render"""

    def __init__(self, font, width, height):
        """Constructor method"""
        lines = __doc__.split('\n')
        self.font = font
        self.dim = (680, len(lines) * 22 + 12)
        self.pos = (0.5 * width - 0.5 * self.dim[0], 0.5 * height - 0.5 * self.dim[1])
        self.seconds_left = 0
        self.surface = pygame.Surface(self.dim)
        self.surface.fill((0, 0, 0, 0))
        for i, line in enumerate(lines):
            text_texture = self.font.render(line, True, (255, 255, 255))
            self.surface.blit(text_texture, (22, i * 22))
            self._render = False
        self.surface.set_alpha(220)

    def toggle(self):
        """Toggle on or off the render help"""
        self._render = not self._render

    def render(self, display):
        """Render help text method"""
        if self._render:
            display.blit(self.surface, self.pos)


# ==============================================================================
# -- CollisionSensor -----------------------------------------------------------
# ==============================================================================


class CollisionSensor(object):
    """ Class for collision sensors"""

    def __init__(self, parent_actor, hud):
        """Constructor method"""
        self.sensor = None
        self.history = []
        self._parent = parent_actor
        self.hud = hud
        world = self._parent.get_world()
        blueprint = world.get_blueprint_library().find('sensor.other.collision')
        self.sensor = world.spawn_actor(blueprint, carla.Transform(), attach_to=self._parent)
        # We need to pass the lambda a weak reference to
        # self to avoid circular reference.
        weak_self = weakref.ref(self)
        self.sensor.listen(lambda event: CollisionSensor._on_collision(weak_self, event))

    def get_collision_history(self):
        """Gets the history of collisions"""
        history = collections.defaultdict(int)
        for frame, intensity in self.history:
            history[frame] += intensity
        return history

    @staticmethod
    def _on_collision(weak_self, event):
        """On collision method"""
        self = weak_self()
        if not self:
            return
        actor_type = get_actor_display_name(event.other_actor)
        self.hud.notification('Collision with %r' % actor_type)
        impulse = event.normal_impulse
        intensity = math.sqrt(impulse.x ** 2 + impulse.y ** 2 + impulse.z ** 2)
        self.history.append((event.frame, intensity))
        if len(self.history) > 4000:
            self.history.pop(0)


# ==============================================================================
# -- LaneInvasionSensor --------------------------------------------------------
# ==============================================================================


class LaneInvasionSensor(object):
    """Class for lane invasion sensors"""

    def __init__(self, parent_actor, hud):
        """Constructor method"""
        self.sensor = None
        self._parent = parent_actor
        self.hud = hud
        world = self._parent.get_world()
        bp = world.get_blueprint_library().find('sensor.other.lane_invasion')
        self.sensor = world.spawn_actor(bp, carla.Transform(), attach_to=self._parent)
        # We need to pass the lambda a weak reference to self to avoid circular
        # reference.
        weak_self = weakref.ref(self)
        self.sensor.listen(lambda event: LaneInvasionSensor._on_invasion(weak_self, event))

    @staticmethod
    def _on_invasion(weak_self, event):
        """On invasion method"""
        self = weak_self()
        if not self:
            return
        lane_types = set(x.type for x in event.crossed_lane_markings)
        text = ['%r' % str(x).split()[-1] for x in lane_types]
        self.hud.notification('Crossed line %s' % ' and '.join(text))


# ==============================================================================
# -- GnssSensor --------------------------------------------------------
# ==============================================================================


class GnssSensor(object):
    """ Class for GNSS sensors"""

    def __init__(self, parent_actor):
        """Constructor method"""
        self.sensor = None
        self._parent = parent_actor
        self.lat = 0.0
        self.lon = 0.0
        world = self._parent.get_world()
        blueprint = world.get_blueprint_library().find('sensor.other.gnss')
        self.sensor = world.spawn_actor(blueprint, carla.Transform(carla.Location(x=1.0, z=2.8)),
                                        attach_to=self._parent)
        # We need to pass the lambda a weak reference to
        # self to avoid circular reference.
        weak_self = weakref.ref(self)
        self.sensor.listen(lambda event: GnssSensor._on_gnss_event(weak_self, event))

    @staticmethod
    def _on_gnss_event(weak_self, event):
        """GNSS method"""
        self = weak_self()
        if not self:
            return
        self.lat = event.latitude
        self.lon = event.longitude


# ==============================================================================
# -- CameraManager -------------------------------------------------------------
# ==============================================================================


class CameraManager(object):
    """ Class for camera management"""

    def __init__(self, parent_actor, hud):
        """Constructor method"""
        self.sensor = None
        self.surface = None
        self._parent = parent_actor
        self.hud = hud
        self.recording = False
        bound_x = 0.5 + self._parent.bounding_box.extent.x
        bound_y = 0.5 + self._parent.bounding_box.extent.y
        bound_z = 0.5 + self._parent.bounding_box.extent.z
        attachment = carla.AttachmentType
        self._camera_transforms = [
            (carla.Transform(carla.Location(x=-2.0 * bound_x, y=+0.0 * bound_y, z=2.0 * bound_z),
                             carla.Rotation(pitch=8.0)), attachment.SpringArm),
            (carla.Transform(carla.Location(x=+0.8 * bound_x, y=+0.0 * bound_y, z=1.3 * bound_z)), attachment.Rigid),
            (
            carla.Transform(carla.Location(x=+1.9 * bound_x, y=+1.0 * bound_y, z=1.2 * bound_z)), attachment.SpringArm),
            (carla.Transform(carla.Location(x=-2.8 * bound_x, y=+0.0 * bound_y, z=4.6 * bound_z),
                             carla.Rotation(pitch=6.0)), attachment.SpringArm),
            (carla.Transform(carla.Location(x=-1.0, y=-1.0 * bound_y, z=0.4 * bound_z)), attachment.Rigid)]

        self.transform_index = 1
        self.sensors = [
            ['sensor.camera.rgb', cc.Raw, 'Camera RGB'],
            ['sensor.camera.depth', cc.Raw, 'Camera Depth (Raw)'],
            ['sensor.camera.depth', cc.Depth, 'Camera Depth (Gray Scale)'],
            ['sensor.camera.depth', cc.LogarithmicDepth, 'Camera Depth (Logarithmic Gray Scale)'],
            ['sensor.camera.semantic_segmentation', cc.Raw, 'Camera Semantic Segmentation (Raw)'],
            ['sensor.camera.semantic_segmentation', cc.CityScapesPalette,
             'Camera Semantic Segmentation (CityScapes Palette)'],
            ['sensor.lidar.ray_cast', None, 'Lidar (Ray-Cast)']]
        world = self._parent.get_world()
        bp_library = world.get_blueprint_library()
        for item in self.sensors:
            blp = bp_library.find(item[0])
            if item[0].startswith('sensor.camera'):
                blp.set_attribute('image_size_x', str(hud.dim[0]))
                blp.set_attribute('image_size_y', str(hud.dim[1]))
            elif item[0].startswith('sensor.lidar'):
                blp.set_attribute('range', '50')
            item.append(blp)
        self.index = None

    def toggle_camera(self):
        """Activate a camera"""
        self.transform_index = (self.transform_index + 1) % len(self._camera_transforms)
        self.set_sensor(self.index, notify=False, force_respawn=True)

    def set_sensor(self, index, notify=True, force_respawn=False):
        """Set a sensor"""
        index = index % len(self.sensors)
        needs_respawn = True if self.index is None else (
                force_respawn or (self.sensors[index][0] != self.sensors[self.index][0]))
        if needs_respawn:
            if self.sensor is not None:
                self.sensor.destroy()
                self.surface = None
            self.sensor = self._parent.get_world().spawn_actor(
                self.sensors[index][-1],
                self._camera_transforms[self.transform_index][0],
                attach_to=self._parent,
                attachment_type=self._camera_transforms[self.transform_index][1])

            # We need to pass the lambda a weak reference to
            # self to avoid circular reference.
            weak_self = weakref.ref(self)
            self.sensor.listen(lambda image: CameraManager._parse_image(weak_self, image))
        if notify:
            self.hud.notification(self.sensors[index][2])
        self.index = index

    def next_sensor(self):
        """Get the next sensor"""
        self.set_sensor(self.index + 1)

    def toggle_recording(self):
        """Toggle recording on or off"""
        self.recording = not self.recording
        self.hud.notification('Recording %s' % ('On' if self.recording else 'Off'))

    def render(self, display):
        """Render method"""
        if self.surface is not None:
            display.blit(self.surface, (0, 0))

    @staticmethod
    def _parse_image(weak_self, image):
        self = weak_self()
        if not self:
            return
        if self.sensors[self.index][0].startswith('sensor.lidar'):
            points = np.frombuffer(image.raw_data, dtype=np.dtype('f4'))
            points = np.reshape(points, (int(points.shape[0] / 4), 4))
            lidar_data = np.array(points[:, :2])
            lidar_data *= min(self.hud.dim) / 100.0
            lidar_data += (0.5 * self.hud.dim[0], 0.5 * self.hud.dim[1])
            lidar_data = np.fabs(lidar_data)  # pylint: disable=assignment-from-no-return
            lidar_data = lidar_data.astype(np.int32)
            lidar_data = np.reshape(lidar_data, (-1, 2))
            lidar_img_size = (self.hud.dim[0], self.hud.dim[1], 3)
            lidar_img = np.zeros(lidar_img_size)
            lidar_img[tuple(lidar_data.T)] = (255, 255, 255)
            self.surface = pygame.surfarray.make_surface(lidar_img)
        else:
            image.convert(self.sensors[self.index][1])
            array = np.frombuffer(image.raw_data, dtype=np.dtype("uint8"))
            array = np.reshape(array, (image.height, image.width, 4))
            array = array[:, :, :3]
            array = array[:, :, ::-1]
            self.surface = pygame.surfarray.make_surface(array.swapaxes(0, 1))
        if self.recording:
            image.save_to_disk('_out/%08d' % image.frame)


# ==============================================================================
# -- Game Loop ---------------------------------------------------------
# ==============================================================================


def game_loop(args):
    """
    Main loop of the simulation. It handles updating all the HUD information,
    ticking the agent and, if needed, the world.
    """

    pygame.init()
    pygame.font.init()
    world = None

    try:
        if args.seed:
            random.seed(args.seed)

        client = carla.Client(args.host, args.port)
        client.set_timeout(60.0)

        traffic_manager = client.get_trafficmanager()
        sim_world = client.get_world()

        if args.sync:
            settings = sim_world.get_settings()
            settings.synchronous_mode = True
            settings.fixed_delta_seconds = 0.05
            sim_world.apply_settings(settings)

            traffic_manager.set_synchronous_mode(True)

        display = pygame.display.set_mode(
            (args.width, args.height),
            pygame.HWSURFACE | pygame.DOUBLEBUF)

        hud = HUD(args.width, args.height)
        world = World(client.get_world(), hud, args)
        controller = KeyboardControl(world)

        if args.agent == "Basic":
            agent = BasicAgent(world.player, 30)
            agent.follow_speed_limits(True)
        elif args.agent == "Behavior":
            agent = BehaviorAgent(world.player, behavior=args.behavior)

        # 设定目标点
        spawn_points = world.map.get_spawn_points()
        destination = random.choice(spawn_points).location
        agent.set_destination(destination)

        # 加载你的模型
        device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        my_model =  Transfollower(config = HighDSettings).to(device)  # 确保 MyNeuralNetwork() 定义正确
        my_model2 =  Transfollower(config = HighDSettings).to(device)
        my_model = torch.load(model_path_1, map_location=device)
        my_model2 = torch.load(model_path_2, map_location=device)

        # **设置为评估模式（推理时需要）**
        my_model.eval()
        my_model2.eval()
        # **维护一个120帧的双端队列**
        frame_buffer = collections.deque(maxlen=120)  # 只保留最近 120 帧
        clock = pygame.time.Clock()
        frame = 1  # 计数帧数

        while True:
            clock.tick()
            if args.sync:
                world.world.tick()
            else:
                world.world.wait_for_tick()
            if controller.parse_events():
                return

            world.tick(clock)
            world.render(display)
            pygame.display.flip()

            # 🚦 显示车道信息
            lane_id, lane_type, is_intersection, has_stop_sign, left_traffic_light, straight_traffic_light, right_traffic_light = get_lane_info(world.player)
            world.hud.notification(
                f"Lane: {lane_id}, Type: {lane_type}, Int: {is_intersection}, STOP: {has_stop_sign}, "
                f"Left TL: {left_traffic_light}, Straight TL: {straight_traffic_light}, Right TL: {right_traffic_light}",
                seconds=2.0
            )

            # **收集当前帧数据**
            current_frame_data = log_frame_data(world.world, world.player)
            assert len(current_frame_data) == 60, f"数据维度错误: {len(current_frame_data)} 不是 60"
            frame_buffer.append(current_frame_data)  # 加入缓冲区

            # **前 120 帧使用 CARLA 代理**
            if frame <= 120:
                control = agent.run_step()
                control.manual_gear_shift = False
                world.player.apply_control(control)

            # **从第 121 帧开始，使用模型预测**
            else:
                #########################vy的
                # **构造 `enc_inp`** (过去120帧, 60维)
                enc_inp = torch.tensor(list(frame_buffer), dtype=torch.float32).unsqueeze(0).to(device)[:,:60,:]  # (B=1, 120, 60)

                # **构造 `dec_inp`** (未来80帧, 59维)
                batch_y = torch.tensor(list(frame_buffer), dtype=torch.float32).unsqueeze(0).to(device)[:,-80:,0].unsqueeze(-1)  # 变成 (1, 120, 60)

                dec_inp = torch.zeros([batch_y.shape[0], 60, batch_y.shape[-1]]).float().to(device) + \
                          batch_y[:, :20, :].mean(axis=1, keepdim=True)

                dec_inp = torch.cat([batch_y[:, :20, :], dec_inp], dim=1).float().to(device)

                batch_y_mark = torch.tensor(list(frame_buffer), dtype=torch.float32).unsqueeze(0).to(device)[:,40:120,2:]  # 这里假设时间标记信息

                dec_inp = torch.cat([dec_inp, batch_y_mark], axis=-1)  # (1, 80, 59)

                #############################vx的
                batch_y2 = torch.tensor(list(frame_buffer), dtype=torch.float32).unsqueeze(0).to(device)[:, -80:,1].unsqueeze(-1)  # 变成 (1, 120, 60)
                dec_inp2 = torch.zeros([batch_y2.shape[0], 60, batch_y2.shape[-1]]).float().to(device) + \
                          batch_y2[:, :20, :].mean(axis=1, keepdim=True)

                dec_inp2 = torch.cat([batch_y2[:, :20, :], dec_inp2], dim=1).float().to(device)

                dec_inp2 = torch.cat([dec_inp2, batch_y_mark], axis=-1)  # (1, 80, 59)
                # **模型预测未来 60 帧的横向 & 纵向速度**
                with torch.no_grad():
                    output = my_model(enc_inp, dec_inp)  # (1, 60, 2)
                    v_y_pred = output[0][:, 0]
                    output2 = my_model2(enc_inp, dec_inp2)  # (1, 60, 2)
                    v_x_pred = output2[0][:, 0]###########这里vx是横向速度了
                    print(v_y_pred,v_x_pred)
                # **用预测值控制车辆**
                control = carla.VehicleControl()
                control.throttle = min(max(v_x_pred[-1] .item() / 10.0, 0.0), 1.0)  # 取最新的预测值
                control.steer = min(max(v_y_pred[-1] .item() / 5.0, -1.0), 1.0)
                world.player.apply_control(control)

            # 🎯 **目标点到达处理**
            if agent.done():
                if args.loop:
                    agent.set_destination(random.choice(spawn_points).location)
                    world.hud.notification("Target reached", seconds=4.0)
                    print("The target has been reached, searching for another target")
                else:
                    print("The target has been reached, stopping the simulation")
                    break

            frame += 1  # 帧数 +1

    finally:
        if world is not None:
            settings = world.world.get_settings()
            settings.synchronous_mode = False
            settings.fixed_delta_seconds = None
            world.world.apply_settings(settings)
            traffic_manager.set_synchronous_mode(True)

            world.destroy()

        pygame.quit()

# ==============================================================================
# -- main() --------------------------------------------------------------
# ==============================================================================


def main():
    """Main method"""

    argparser = argparse.ArgumentParser(
        description='CARLA Automatic Control Client')
    argparser.add_argument(
        '-v', '--verbose', action='store_true', dest='debug',
        help='Print debug information')
    argparser.add_argument(
        '--host', metavar='H', default='127.0.0.1',
        help='IP of the host server (default: 127.0.0.1)')
    argparser.add_argument(
        '-p', '--port', metavar='P', default=2000, type=int,
        help='TCP port to listen to (default: 2000)')
    argparser.add_argument(
        '--res', metavar='WIDTHxHEIGHT', default='1280x720',
        help='Window resolution (default: 1280x720)')
    argparser.add_argument(
        '--sync', action='store_true',
        help='Synchronous mode execution')
    argparser.add_argument(
        '--filter', metavar='PATTERN', default='model3',
        help='Actor filter (default: "vehicle.*")')
    argparser.add_argument(
        '--generation', metavar='G', default='All',
        help='restrict to certain actor generation (values: "2","3","All" - default: "All")')
    argparser.add_argument(
        '-l', '--loop', action='store_true', dest='loop',
        help='Sets a new random destination upon reaching the previous one (default: False)')
    argparser.add_argument(
        "-a", "--agent", type=str, choices=["Behavior", "Basic", "Constant"], default="Behavior",
        help="select which agent to run")
    argparser.add_argument(
        '-b', '--behavior', type=str, choices=["cautious", "normal", "aggressive"], default='normal',
        help='Choose one of the possible agent behaviors (default: normal)')
    argparser.add_argument(
        '-s', '--seed', default=None, type=int,
        help='Set seed for repeating executions (default: None)')

    args = argparser.parse_args()

    args.width, args.height = [int(x) for x in args.res.split('x')]

    log_level = logging.DEBUG if args.debug else logging.INFO
    logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)

    logging.info('listening to server %s:%s', args.host, args.port)

    print(__doc__)

    try:
        game_loop(args)

    except KeyboardInterrupt:
        print('\nCancelled by user. Bye!')


if __name__ == '__main__':
    main()
